// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

.syntax unified
.thumb

#include <AsmOffsets.inc>         // generated by the build from AsmOffsets.cpp
#include <unixasmmacros.inc>

#define STACKSIZEOF_ExInfo ((SIZEOF__ExInfo + 7)&(~7))

#define rsp_offsetof_ExInfo  0
#define rsp_offsetof_Context STACKSIZEOF_ExInfo

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RhpThrowHwEx
//
// INPUT:  R0:  exception code of fault
//         R1:  faulting RIP
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpThrowHwEx, _TEXT, NoHandler

        mov         r2, r0         // save exception code into r2
        mov         r0, sp         // get SP of fault site

        mov         lr, r1         // set IP of fault site

        // Setup a PAL_LIMITED_CONTEXT on the stack {
        PROLOG_VPUSH {d8-d15}
        PROLOG_PUSH "{r0,lr}"        // push {sp, pc} of fault site
        PROLOG_PUSH "{r0,r4-r11,lr}"
        // } end PAL_LIMITED_CONTEXT

        PROLOG_STACK_ALLOC STACKSIZEOF_ExInfo

        // r0: SP of fault site
        // r1: IP of fault site
        // r2: exception code of fault
        // lr: IP of fault site (as a 'return address')
        mov         r4, r2 // save exception code of fault

        // r0 = GetThread()
        INLINE_GETTHREAD

        // r1 <- ExInfo*
        add         r1, sp, #rsp_offsetof_ExInfo
        mov         r3, #0
        str         r3, [r1, #OFFSETOF__ExInfo__m_exception]        // pExInfo->m_exception = null
        mov         r3, #1
        strb        r3, [r1, #OFFSETOF__ExInfo__m_passNumber]       // pExInfo->m_passNumber = 1
        mov         r3, #0xFFFFFFFF
        str         r3, [r1, #OFFSETOF__ExInfo__m_idxCurClause]     // pExInfo->m_idxCurClause = MaxTryRegionIdx
        mov         r3, #2
        strb        r3, [r1, #OFFSETOF__ExInfo__m_kind]             // pExInfo->m_kind = ExKind.HardwareFault

        // link the ExInfo into the thread's ExInfo chain
        ldr         r3, [r0, #OFFSETOF__Thread__m_pExInfoStackHead]
        str         r3, [r1, #OFFSETOF__ExInfo__m_pPrevExInfo]      // pExInfo->m_pPrevExInfo = m_pExInfoStackHead
        str         r1, [r0, #OFFSETOF__Thread__m_pExInfoStackHead] // m_pExInfoStackHead = pExInfo

        // set the exception context field on the ExInfo
        add         r2, sp, #rsp_offsetof_Context                   // r2 <- PAL_LIMITED_CONTEXT*
        str         r2, [r1, #OFFSETOF__ExInfo__m_pExContext]       // pExInfo->m_pExContext = pContext

        mov         r0, r4 	        // restore the exception code
        // r0 contains the exception code
        // r1 contains the address of the ExInfo
        bl          C_FUNC(RhThrowHwEx)

GLOBAL_LABEL RhpThrowHwEx2

        // no return
        EMIT_BREAKPOINT

NESTED_END RhpThrowHwEx


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RhpThrowEx
//
// INPUT:  R0:  exception object
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler

        // Setup a PAL_LIMITED_CONTEXT on the stack {
        PROLOG_VPUSH {d8-d15}
        PROLOG_PUSH "{r0,lr}"        	// Reserve space for SP and store LR
        PROLOG_PUSH "{r0,r4-r11,lr}"
        // } end PAL_LIMITED_CONTEXT

        PROLOG_STACK_ALLOC STACKSIZEOF_ExInfo

        // Calculate  SP at callsite and save into the PAL_LIMITED_CONTEXT
        add         r4, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT)
        str         r4, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__SP)]

        mov         r4, r0 // Save exception object
        // r0 = GetThread()
        INLINE_GETTHREAD

        add         r2, sp, #(rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 0x8) 	// r2 <- addr of return address

        // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic. So  the return
        // address could have been hijacked when we were in that C# code and we must remove the hijack and
        // reflect the correct return address in our exception context record.  The other throw helpers don't
        // need this because they cannot be tail-called from C#.
        // NOTE: we cannot use INLINE_THREAD_UNHIJACK because it will write into the stack at the location
        // where the tail-calling thread had saved LR, which may not match where we have saved LR.

        ldr         r1, [r0, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
        cbz         r1, LOCAL_LABEL(NotHiJacked)

        ldr         r3, [r0, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]

        // r4: exception object
        // r1: hijacked return address
        // r0: pThread
        // r3: hijacked return address location

        add         r12, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT)        // re-compute SP at callsite
        cmp         r3, r12             // if (m_ppvHijackedReturnAddressLocation < SP at callsite)
        blo         LOCAL_LABEL(TailCallWasHijacked)

        // normal case where a valid return address location is hijacked
        str         r1, [r3]
        b           LOCAL_LABEL(ClearThreadState)

LOCAL_LABEL(TailCallWasHijacked):

        // Abnormal case where the return address location is now invalid because we ended up here via a tail
        // call.  In this case, our hijacked return address should be the correct caller of this method.
        //

        // stick the previous return address in LR as well as in the right spots in our PAL_LIMITED_CONTEXT.
        mov         lr, r1
        str         lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__LR)]
        str         lr, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP)]

LOCAL_LABEL(ClearThreadState):

        // clear the Thread's hijack state
        mov         r3, #0
        str         r3, [r0, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
        str         r3, [r0, #OFFSETOF__Thread__m_pvHijackedReturnAddress]

LOCAL_LABEL(NotHiJacked):

        add         r1, sp, #rsp_offsetof_ExInfo                    // r1 <- ExInfo*
        mov         r3, #0
        str         r3, [r1, #OFFSETOF__ExInfo__m_exception] 	      // init the exception object to null
        mov         r3, #1
        strb        r3, [r1, #OFFSETOF__ExInfo__m_passNumber]       // init to the first pass
        strb        r3, [r1, #OFFSETOF__ExInfo__m_kind]
        mov         r3, #0xFFFFFFFF
        str         r3, [r1, #OFFSETOF__ExInfo__m_idxCurClause]     // ExKind.Throw

        // link the ExInfo into the thread's ExInfo chain
        ldr         r3, [r0, #OFFSETOF__Thread__m_pExInfoStackHead]
        str         r3, [r1, #OFFSETOF__ExInfo__m_pPrevExInfo]  	  // pExInfo->m_pPrevExInfo = m_pExInfoStackHead
        str         r1, [r0, #OFFSETOF__Thread__m_pExInfoStackHead] // m_pExInfoStackHead = pExInfo

        // set the exception context field on the ExInfo
        add         r3, sp, #rsp_offsetof_Context             // r3 <- PAL_LIMITED_CONTEXT*
        str         r3, [r1, #OFFSETOF__ExInfo__m_pExContext] // init ExInfo.m_pExContext

        mov         r0, r4 // Restore exception object
        // r0 contains the exception object
        // r1 contains the address of the new ExInfo
        bl          C_FUNC(RhThrowEx)

GLOBAL_LABEL RhpThrowEx2

        // no return
        EMIT_BREAKPOINT

NESTED_END RhpThrowEx, _TEXT


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void FASTCALL RhpRethrow()
//
// SUMMARY:  Similar to RhpThrowEx, except that it passes along the currently active ExInfo
//
// INPUT:
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpRethrow, _TEXT, NoHandler

        // Setup a PAL_LIMITED_CONTEXT on the stack {
        PROLOG_VPUSH {d8-d15}
        PROLOG_PUSH "{r0,lr}"         // Reserve space for SP and store LR
        PROLOG_PUSH "{r0,r4-r11,lr}"
        // } end PAL_LIMITED_CONTEXT

        PROLOG_STACK_ALLOC STACKSIZEOF_ExInfo

        // Compute and save SP at callsite.
        add         r1, sp, #(STACKSIZEOF_ExInfo + SIZEOF__PAL_LIMITED_CONTEXT)
        str         r1, [sp, #(rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__SP)]

        // r0 = GetThread();
        INLINE_GETTHREAD

        // r1 <- ExInfo*
        add         r1, sp, #rsp_offsetof_ExInfo

        mov         r3, #0
        str         r3, [r1, #OFFSETOF__ExInfo__m_exception]         // init the exception object to null
        strb        r3, [r1, #OFFSETOF__ExInfo__m_kind]              // init to a deterministic value (ExKind.None)
        mov         r3, #1
        strb        r3, [r1, #OFFSETOF__ExInfo__m_passNumber]        // pExInfo->m_passNumber = 1
        mov         r3, #0xFFFFFFFF
        str         r3, [r1, #OFFSETOF__ExInfo__m_idxCurClause]

        // link the ExInfo into the thread's ExInfo chain
        ldr         r3, [r0, #OFFSETOF__Thread__m_pExInfoStackHead] 	// r3 <- currently active ExInfo
        str         r3, [r1, #OFFSETOF__ExInfo__m_pPrevExInfo]        // pExInfo->m_pPrevExInfo = m_pExInfoStackHead
        str         r1, [r0, #OFFSETOF__Thread__m_pExInfoStackHead]   // m_pExInfoStackHead = pExInfo

        // set the exception context field on the ExInfo
        add         r2, sp, #rsp_offsetof_Context                 	  // r2 <- PAL_LIMITED_CONTEXT*
        str         r2, [r1, #OFFSETOF__ExInfo__m_pExContext]         // init ExInfo.m_pExContext

        mov         r0, r3
        // r0 contains the currently active ExInfo
        // r1 contains the address of the new ExInfo
        blx    C_FUNC(RhRethrow)

GLOBAL_LABEL RhpRethrow2

        // no return
        EMIT_BREAKPOINT

NESTED_END RhpRethrow, _TEXT


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void* FASTCALL RhpCallCatchFunclet(OBJECTREF exceptionObj,
//                                    void* pHandlerIP,
//                                    REGDISPLAY* pRegDisplay,
//                                    ExInfo* pExInfo)
//
// INPUT:  R0:  exception object
//         R1:  handler funclet address
//         R2:  REGDISPLAY*
//         R3:  ExInfo*
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpCallCatchFunclet, _TEXT, NoHandler

        PROLOG_PUSH "{r0,r2-r11,lr}" // r0, r2 & r3 are saved so we have the exception object,
	                                   // REGDISPLAY and ExInfo later
        PROLOG_VPUSH {d8-d15}

#define rsp_offset_r0 (8 * 8)
#define rsp_offset_r2 (8 * 8) + 4
#define rsp_offset_r3 (8 * 8) + 8

        mov         r4, r0 // Save exception object
        mov         r5, r1 // Save handler funclet address
        mov         r6, r2 // Save REGDISPLAY*

        // Clear the DoNotTriggerGc state before calling out to our managed catch funclet,
        // trashes r0-r2.
        // r0 = GetThread()
        INLINE_GETTHREAD

LOCAL_LABEL(ClearRetry_Catch):
        ldrex       r1, [r0, #OFFSETOF__Thread__m_ThreadStateFlags]
        bics        r1, #TSF_DoNotTriggerGc
        strex       r2, r1, [r0, #OFFSETOF__Thread__m_ThreadStateFlags]
        cbz         r2, LOCAL_LABEL(ClearSuccess_Catch)
        b           LOCAL_LABEL(ClearRetry_Catch)
LOCAL_LABEL(ClearSuccess_Catch):

        mov         r0, r4 // Reload exception object
        mov         r3, r5 // Reload handler funclet address
        mov         r2, r6 // Reload REGDISPLAY pointer

        //
        // set preserved regs to the values expected by the funclet
        //
        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR4]
        ldr         r4, [r12]
        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR5]
        ldr         r5, [r12]
        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR6]
        ldr         r6, [r12]
        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR7]
        ldr         r7, [r12]
        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR8]
        ldr         r8, [r12]
        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR9]
        ldr         r9, [r12]
        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR10]
        ldr         r10, [r12]
        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR11]
        ldr         r11, [r12]

        //
        // load vfp preserved regs
        //
        add         r12, r2, #OFFSETOF__REGDISPLAY__D
        vldm        r12!, {d8-d15}

        // r0 <- exception object
        blx         r3                                 // call handler funclet

GLOBAL_LABEL RhpCallCatchFunclet2

        str         r0, [sp, #rsp_offset_r0]             // Save the result

        INLINE_GETTHREAD                                 // r0 <- Thread*
        // We must unhijack the thread at this point because the section of stack where the
        // hijack is applied may go dead. If it does, then the next time we try to unhijack
        // the thread, it will corrupt the stack.
        INLINE_THREAD_UNHIJACK r0, r3, r12               // Thread in r0, trashes r3 and r1
        ldr         r2, [sp, #rsp_offset_r2]             // r2 <- REGDISPLAY*
        ldr         r3, [sp, #rsp_offset_r3]             // r3 <- current ExInfo*
        ldr         r2, [r2, #OFFSETOF__REGDISPLAY__SP]  // r2 <- resume SP value

LOCAL_LABEL(PopExInfoLoop):
        ldr         r3, [r3, #OFFSETOF__ExInfo__m_pPrevExInfo]  // r3 <- next ExInfo
        cbz         r3, LOCAL_LABEL(DonePopping)                // if (pExInfo == null) { we're done }
        cmp         r3, r2
        blt         LOCAL_LABEL(PopExInfoLoop)                  // if (pExInfo < resume SP} { keep going }
LOCAL_LABEL(DonePopping):

        str         r3, [r0, #OFFSETOF__Thread__m_pExInfoStackHead]   // store the new head on the Thread

        ldr         r0, [sp, #rsp_offset_r0]             // Load the result

        // reset RSP and jump to the continuation address
        mov         sp, r2
        bx          r0

NESTED_END RhpCallCatchFunclet, _TEXT

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void FASTCALL RhpCallFinallyFunclet(void* pHandlerIP, REGDISPLAY* pRegDisplay)
//
// INPUT:  R0:  handler funclet address
//         R1:  REGDISPLAY*
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpCallFinallyFunclet, _TEXT, NoHandler

        PROLOG_PUSH "{r1,r4-r11,lr}"  // r1 is saved so we have the REGDISPLAY later
        PROLOG_VPUSH {d8-d15}
#define rsp_offset_r1 8 * 8

        //
        // We want to suppress hijacking between invocations of subsequent finallys. We do
        // this because we cannot tolerate a GC after one finally has run (and possibly
        // side-effected the GC state of the method) and then been popped off the stack,
        // leaving behind no trace of its effect.
        //
        // So we clear the state before and set it after invocation of the handler.
        //

        mov         r4, r0 // Save handler funclet address
        mov         r5, r1 // Save REGDISPLAY*
        //
        // clear the DoNotTriggerGc flag, trashes r0-r2
        //
        INLINE_GETTHREAD  // r0 <- Thread*

LOCAL_LABEL(ClearRetry):
        ldrex       r1, [r0, #OFFSETOF__Thread__m_ThreadStateFlags]
        bics        r1, #TSF_DoNotTriggerGc
        strex       r2, r1, [r0, #OFFSETOF__Thread__m_ThreadStateFlags]
        cbz         r2, LOCAL_LABEL(ClearSuccess)
        b           LOCAL_LABEL(ClearRetry)
LOCAL_LABEL(ClearSuccess):

        mov         r2, r4        // reload handler funclet address
        mov         r1, r5        // reload REGDISPLAY pointer

        //
        // set preserved regs to the values expected by the funclet
        //
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR4]
        ldr         r4, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR5]
        ldr         r5, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR6]
        ldr         r6, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR7]
        ldr         r7, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR8]
        ldr         r8, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR9]
        ldr         r9, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR10]
        ldr         r10, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR11]
        ldr         r11, [r12]

        //
        // load vfp preserved regs
        //
        add         r12, r1, #OFFSETOF__REGDISPLAY__D
        vldm        r12!, {d8-d15}

        blx         r2                                  // handler funclet address

GLOBAL_LABEL RhpCallFinallyFunclet2

        ldr         r1, [sp, #rsp_offset_r1]        // reload REGDISPLAY pointer

        //
        // save new values of preserved regs into REGDISPLAY
        //
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR4]
        str         r4, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR5]
        str         r5, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR6]
        str         r6, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR7]
        str         r7, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR8]
        str         r8, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR9]
        str         r9, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR10]
        str         r10, [r12]
        ldr         r12, [r1, #OFFSETOF__REGDISPLAY__pR11]
        str         r11, [r12]

        //
        // store vfp preserved regs
        //
        add         r12, r1, #OFFSETOF__REGDISPLAY__D
        vstm        r12!, {d8-d15}

        //
        // set the DoNotTriggerGc flag, trashes r0-r2
        //
        INLINE_GETTHREAD      // r0 <- Thread*
LOCAL_LABEL(SetRetry):
        ldrex       r1, [r0, #OFFSETOF__Thread__m_ThreadStateFlags]
        orrs        r1, #TSF_DoNotTriggerGc
        strex       r2, r1, [r0, #OFFSETOF__Thread__m_ThreadStateFlags]
        cbz         r2, LOCAL_LABEL(SetSuccess)
        b           LOCAL_LABEL(SetRetry)
LOCAL_LABEL(SetSuccess):

        EPILOG_VPOP {d8-d15}
        EPILOG_POP  "{r1,r4-r11,pc}"

NESTED_END RhpCallFinallyFunclet, _TEXT

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void* FASTCALL RhpCallFilterFunclet(OBJECTREF exceptionObj, void* pFilterIP, REGDISPLAY* pRegDisplay)
//
// INPUT:  R0:  exception object
//         R1:  filter funclet address
//         R2:  REGDISPLAY*
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpCallFilterFunclet, _TEXT, NoHandler

        PROLOG_PUSH "{r2,r4-r11,lr}"
        PROLOG_VPUSH {d8-d15}

        ldr         r12, [r2, #OFFSETOF__REGDISPLAY__pR11]
        ldr         r11, [r12]

        mov         r12, r1                              // r12 <- handler funclet address
        // r0 still contains the exception object
        ldr         r1, [r2, #OFFSETOF__REGDISPLAY__SP]  // r1 <- establisher frame

        //
        // call the funclet
        //    r0 = exception object
        //    r1 = establisher frame
        blx         r12

GLOBAL_LABEL RhpCallFilterFunclet2

        // R0 contains the result of the filter execution

        EPILOG_VPOP {d8-d15}
        EPILOG_POP  "{r2,r4-r11,pc}"

NESTED_END RhpCallFilterFunclet, _TEXT
